package msu.ml.gis;

import msu.ml.data.*;
import java.io.*;
import java.util.*;

/**
 * The ShapefileWriter writes shapefiles to disk. This includes the
 * main shapefile (.shp) as well as the index file (.shx) and the
 * DBF attribute data file (.dbf).
 *
 * @author Reginald M Mead
 * @version 1.0
 */
public class ShapefileWriter 
{

	/**
	 * Create a new ShapefileWriter
	 */
	public ShapefileWriter()
	{

	}

	/**
	 * Write the specified shapefile to disk
	 * 
	 * @param shapefile the shapefile to write
	 * @param basePath the directory path to write to
	 */
	public void write(Shapefile shapefile, String basePath)
	{
		writeMainFile(shapefile, basePath);
		writeIndexFile(shapefile, basePath);
		writeDbfFile(shapefile, basePath);
	}

	/**
	 * Write the DBF file to disk
	 *
	 * @param shapefile shapefile containing the attribute information
	 * @param basePath the directory path to write to
	 */
	protected void writeDbfFile(Shapefile shapefile, String basePath)
	{
		LittleEndianDataOutputStream out;

		try
		{
			out = new LittleEndianDataOutputStream(new FileOutputStream(basePath+".dbf"));
			ArrayList<IShape> shapes = shapefile.getShapes();

			out.write(3); //Type
			out.write(0x6B); //Year since 1900 (0x6B = 107, 1900 + 107 = 2007)
			out.write(7); //Month
			out.write(1); //Day
			out.writeIntLE(shapes.size());
			out.writeShortLE((short)161); //length of header
			out.writeShortLE((short)29); //length of record
			out.fillInt(5);

         ArrayList<DBFAttributeHeader> headers = shapefile.getHeaders();
         for(int i = 0; i < headers.size(); i++)
         {
            DBFAttributeHeader header = headers.get(i);
            byte [] data = header.getName();
            out.write(data, 0, data.length);
            out.fillByte(11 - data.length); //Field Name
            out.writeByte(header.getType());  //Field Type
            out.writeInt(0);                  //address in memory <unused>
            out.writeByte(header.getLength());//Field Size
            out.fillByte(15);
         }
			out.writeByte(0x0D);
			
			//data goes here

			for(int i = 0; i < shapes.size(); i ++)
			{
				out.writeByte(0x20);

            ArrayList<DBFAttribute> attributes = 
               shapes.get(i).getAttributes();

            for(int j = 0; j < attributes.size(); j++)
            {
               byte [] data = attributes.get(i).getValue();
               out.write(data, 0, data.length);
            }
			}
			out.writeByte(0x1A);
			out.close();
		}
		catch(Exception e) { }

	}

	/**
	 * Write the header information for this shapefile 
	 * @param shapefile shapefile containing header information
	 * @param out output stream to write to
	 * @param isIndex true if this is for the index file( used to calculate correct filesize)
	 */
	protected void writeHeader(Shapefile shapefile, LittleEndianDataOutputStream out, boolean isIndex) throws IOException
	{
			out.writeInt(Shapefile.FILE_CODE);
			out.fillInt(5);
			if(isIndex)
				out.writeInt(50 + (4 * shapefile.getNumShapes()));
			else
				out.writeInt(shapefile.getFileSize());
			out.writeIntLE(Shapefile.VERSION);
			out.writeIntLE(shapefile.getShapeType());
			java.awt.geom.Rectangle2D box = shapefile.getBoundingBox();
			out.writeDoubleLE(box.getMinX());
			out.writeDoubleLE(box.getMinY());
			out.writeDoubleLE(box.getMaxX());
			out.writeDoubleLE(box.getMaxY());
			out.fillDouble(4);
	}

	/**
	 * Write the index file for the specified shapefile
	 *
	 * @param shapefile shapefile for this index file
	 * @param basePath the directory path to write to
	 */
	protected void writeIndexFile(Shapefile shapefile, String basePath)
	{
		LittleEndianDataOutputStream out;

		try
		{
			out = new LittleEndianDataOutputStream(new FileOutputStream(basePath+".shx"));
			writeHeader(shapefile, out, true);

			int offset = 50;

			ArrayList<IShape> shapes = shapefile.getShapes();
			for(int i = 0; i < shapes.size(); i ++)
			{
				IShape shape = shapes.get(i);
				out.writeInt(offset);
				out.writeInt(shape.getContentLength());
				offset += (shape.getContentLength() + 4);
			}

			out.close();
		}
		catch(Exception e) { }

	}

	/**
	 * Write the main file for this shapefile (includes actual shape information).
	 * 
	 * @param shapefile the shapefile to write
	 * @param basePath the directory path to write to
	 */
	protected void writeMainFile(Shapefile shapefile, String basePath)
	{
		LittleEndianDataOutputStream out;

		try
		{
			out = new LittleEndianDataOutputStream(new FileOutputStream(basePath+".shp"));
			writeHeader(shapefile, out, false);

			ArrayList<IShape> shapes = shapefile.getShapes();
			for(int i = 0; i < shapes.size(); i ++)
			{
				IShape shape = shapes.get(i);
				out.writeInt(i + 1);
				out.writeInt(shape.getContentLength());
				out.writeIntLE(shape.getShapeType());
				shape.serialize(out);
			}

			out.close();
		}
		catch(Exception e) { }

	}

}
